﻿using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Funq.Tests
{
    [TestClass]
    public class ContainerFixture
    {
        [TestMethod]
        public void RegisterTypeAndGetInstances()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar());

            var container = builder.Build();
            var bar1 = container.Resolve<IBar>();
            var bar2 = container.Resolve<IBar>();

            Assert.IsNotNull(bar1);
            Assert.IsTrue(bar1 is Bar);
            Assert.AreNotSame(bar1, bar2);
        }

        [TestMethod]
        public void ResolvesGetsDependenciesInjected()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar());
            builder.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
            var container = builder.Build();

            var foo = container.Resolve<IFoo>() as Foo;

            Assert.IsNotNull(foo);
            Assert.IsTrue(foo is Foo);
            Assert.IsInstanceOfType(foo.Bar, typeof(Bar));
        }

        [TestMethod]
        public void ConstructorArgumentIsPassedOnResolve()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar, string>((c, s) => new Bar(s));
            var container = builder.Build();

            const string STR_Data = "data";
            var bar = container.Resolve<IBar, string>(STR_Data) as Bar;

            Assert.AreEqual(STR_Data, bar.Arg1);
        }

        [TestMethod]
        public void RegistersMultipleConstructorOverloads()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>((c) => new Bar());
            builder.Register<IBar, string>((c, s) => new Bar(s));
            builder.Register<IBar, string, bool>((c, s, b) => new Bar(s, b));
            var container = builder.Build();

            const string STR_Data = "data";
            var bar1 = container.Resolve<IBar>() as Bar;
            var bar2 = container.Resolve<IBar, string>(STR_Data) as Bar;
            var bar3 = container.Resolve<IBar, string, bool>(STR_Data, true) as Bar;

            Assert.IsNotNull(bar1);
            Assert.IsNotNull(bar2);
            Assert.IsNotNull(bar3);
            Assert.AreEqual(STR_Data, bar2.Arg1);
            Assert.AreEqual(STR_Data, bar3.Arg1);
            Assert.AreEqual(true, bar3.Arg2);
        }

        [TestMethod]
        public void ResolveNamedServices()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar("A")).Named("A");
            builder.Register<IBar>(c => new Bar("B")).Named("B");
            var container = builder.Build();

            var a = container.ResolveNamed<IBar>("A") as Bar;
            var b = container.ResolveNamed<IBar>("B") as Bar;

            Assert.AreEqual("A", a.Arg1);
            Assert.AreEqual("B", b.Arg1);
        }

        [TestMethod]
        public void RegistrationOrderDoesNotMatter()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar("noname"));
            builder.Register<IBar>(c => new Bar("A")).Named("A");
            builder.Register<IBar>(c => new Bar("B")).Named("B");
            var container = builder.Build();

            var a = container.ResolveNamed<IBar>("A") as Bar;
            var b = container.ResolveNamed<IBar>("B") as Bar;
            var noname = container.Resolve<IBar>() as Bar;

            Assert.AreEqual("A", a.Arg1);
            Assert.AreEqual("B", b.Arg1);
            Assert.AreEqual("noname", noname.Arg1);
        }

        [TestMethod]
        public void ResolveNotRegisteredThrowsException()
        {
            var builder = new ContainerBuilder();
            var container = builder.Build();
            try
            {
                var a = container.ResolveNamed<IBar>("A") as Bar;
                Assert.Fail("Should throw exception");
            }
            catch (ResolutionFailedException) { }

            try
            {
                var a = container.Resolve<IBar>() as Bar;
                Assert.Fail("Should throw exception");
            }
            catch (ResolutionFailedException) { }
        }

        [TestMethod]
        public void TryResolveNotRegisteredReturnNull()
        {
            var builder = new ContainerBuilder();
            var container = builder.Build();
            var a = container.TryResolveNamed<IBar>("A") as Bar;
            Assert.IsNull(a);
            var b = container.TryResolve<IBar>() as Bar;
            Assert.IsNull(b);
        }

        [TestMethod]
        public void InstanceCanBeMarkedForReuse()
        {
            var builder = new ContainerBuilder();
            builder.Register<IBar>(c => new Bar()).Scoped(ReuseScope.Container);
            builder.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
            var container = builder.Build();

            var bar1 = container.Resolve<IBar>() as Bar;
            var bar2 = container.Resolve<IBar>() as Bar;
            var foo = container.Resolve<IFoo>() as Foo;
            Assert.AreSame(bar1, foo.Bar);
            Assert.AreSame(bar1, bar2);
        }
    }

    public interface IBar { }
    public interface IFoo { }

    public class Bar : IBar
    {
        public Bar()
        {
        }

        public Bar(string arg)
        {
            this.Arg1 = arg;
        }

        public Bar(string arg1, bool arg2)
        {
            this.Arg1 = arg1;
            this.Arg2 = arg2;
        }

        public string Arg1 { get; private set; }
        public bool Arg2 { get; private set; }
    }

    public class Foo : IFoo
    {
        public IBar Bar { get; private set; }

        public Foo(IBar bar)
        {
            this.Bar = bar;
        }
    }
}
